package editor.graphics;

import blazingbubble.config.Config;
import blazingbubble.exception.StartupException;

import javax.swing.*;
import javax.swing.filechooser.FileNameExtensionFilter;
import javax.media.opengl.GLCapabilities;
import java.awt.*;
import java.awt.event.*;
import java.io.File;
import java.util.Collection;
import java.util.HashMap;

import moltencore.game.loader.Level;
import org.apache.log4j.Logger;
import editor.logic.Editor;
import moltencore.game.loader.LevelSprite;
import spark.graphics.*;
import spark.exception.LoadingException;
import com.javadocking.dock.*;
import com.javadocking.dockable.*;
import com.javadocking.model.FloatDockModel;
import com.javadocking.DockingManager;
import moltencore.game.bubble.Type;

public class Window extends JFrame implements ActionListener, BrushSelectionListener {
	private static Logger logger = Logger.getLogger(Window.class);
	public final static String SPRITE_MAP_PATH = "/graphics/spriteMaps";
	public final static String DEFAULT_THEME_PATH = "/graphics/textures/";
	public final static String PHYSICAL_OBJECTS_THEME_PATH = "/levelEditor/";
	private final static String PHYSICAL_OBJECTS_SPRITE_MAP_PARENT = "/levelEditor/";
	public final static String PHYSICAL_OBJECTS_SPRITE_MAP = "physicalObjects.sm";
	public final static String PHYSICAL_OBJECTS_THEME = "default";
	private final static String ACTION_OPEN_FILE = "open";
	private final static String ACTION_NEW_FILE = "new";
	private final static String ACTION_SPRITE_MAP = "spriteMap";
	private final static String ACTION_SAVE_FILE = "save";
	private final static String ACTION_MISC = "misc";
	private final static String ACTION_PUSH = "push";
	private final static String ACTION_FILL = "fill";
	static final int ICON_DIMENSION = 32;

	private TabDock spriteMapsBot;
	private HashMap<String, DefaultDockable> spriteMapPanels = new HashMap<String, DefaultDockable>();
	private DefaultDockable canvasDockable;
	private JFileChooser spriteMapFileChooser;
	private JFileChooser openLevelFileChooser;
	private EditorCanvas canvas;
	private Editor editor = new Editor();
	private JPanel brushPanel = new JPanel();
	private JFileChooser saveSaveFileChooser;
	private JCheckBox misc = new JCheckBox();
	private JCheckBox push = new JCheckBox();
	private JCheckBox fill = new JCheckBox();

	public Window() throws StartupException {
		editor.newLevel();

		spriteMapFileChooser = new JFileChooser(Config.getResourcePath() + SPRITE_MAP_PATH);
		spriteMapFileChooser.setFileFilter(new FileNameExtensionFilter("Sprite map (*.sm)", "sm"));

		openLevelFileChooser = new JFileChooser(Config.getResourcePath() + "/graphics/levels");
		openLevelFileChooser.setFileFilter(new FileNameExtensionFilter("Blazing editor level (*." + Level.EXTENSION + ")", Level.EXTENSION));

		saveSaveFileChooser = new JFileChooser(Config.getResourcePath() + "/graphics/levels");
		saveSaveFileChooser.setFileFilter(new FileNameExtensionFilter("Blazing editor level (*." + Level.EXTENSION + ")", Level.EXTENSION));

		canvas = new EditorCanvas(new GLCapabilities(), editor);
		canvas.setScene(new Scene());

		setTitle("Blazing Bubble Level Editor");
		setSize(new Dimension(800, 600));

		addWindowListener(new WindowAdapter() {
			public void windowClosing(WindowEvent e) {
				dispose();
			}
		}

		);

		JToolBar toolbar = new JToolBar();

		String iconsPath = "/levelEditor/icons/";
		toolbar.add(makeButton(iconsPath + "new.png", ACTION_NEW_FILE, "New level file", "New"));
		toolbar.add(makeButton(iconsPath + "open.png", ACTION_OPEN_FILE, "Open level file", "Open"));
		toolbar.add(makeButton(iconsPath + "save.png", ACTION_SAVE_FILE, "Save changes to level file", "Save"));
		toolbar.add(makeButton(iconsPath + "spriteMap.png", ACTION_SPRITE_MAP, "Load sprite map", "Sprites"));
		setUpBrushPanel(null);
		toolbar.add(brushPanel);
		JPanel checkboxes = new JPanel();
		checkboxes.add(new JLabel("Misc"));
		checkboxes.add(misc);
		checkboxes.add(new JLabel("Push"));
		checkboxes.add(push);
		checkboxes.add(new JLabel("Fill"));
		checkboxes.add(fill);
		toolbar.add(checkboxes);

		misc.addActionListener(this);
		misc.setActionCommand(ACTION_MISC);
		push.addActionListener(this);
		push.setActionCommand(ACTION_PUSH);
		fill.addActionListener(this);
		fill.setActionCommand(ACTION_FILL);

		getContentPane().add(toolbar, BorderLayout.NORTH);

		JScrollPane canvasPane = new JScrollPane(canvas);
		canvasPane.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_NEVER);
		canvasPane.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
		canvasDockable = new DefaultDockable("canvasDockable", canvasPane, editor.getLevelName(), null);

		SplitDock verticalSplit = new SplitDock();
		SplitDock horizontalSplit = new SplitDock();
		verticalSplit.addChildDock(horizontalSplit, new Position(Position.RIGHT));

		LeafDock physicalObjects = new SingleDock();
		try {
			physicalObjects.addDockable(getPhysicalObjectsSpriteMapPanelDockable(), new Position(0));
		}
		catch(LoadingException e) {
			logger.error("Open physical objects failed", e);
			JOptionPane.showMessageDialog(null,
			                              e.getMessage(),
			                              "Error loading physical object sprites",
			                              JOptionPane.ERROR_MESSAGE);
			throw new StartupException(e);
		}
		verticalSplit.addChildDock(physicalObjects, new Position(Position.LEFT));
		verticalSplit.setDividerLocation(200);

		spriteMapsBot = new TabDock();
		horizontalSplit.addChildDock(spriteMapsBot, new Position(Position.BOTTOM));

		SingleDock canvasDock = new SingleDock();
		canvasDock.addDockable(canvasDockable, new Position(0));
		horizontalSplit.addChildDock(canvasDock, new Position(Position.TOP));
		horizontalSplit.setDividerLocation(400);

		FloatDockModel dockModel = new FloatDockModel();
		dockModel.addOwner(Integer.toString(hashCode()), this);
		dockModel.addRootDock("verticalSplit", verticalSplit, this);
		DockingManager.setDockModel(dockModel);
		getContentPane().add(verticalSplit, BorderLayout.CENTER);
	}

	public void update(float elapsedSeconds) {
		canvas.doFrame(elapsedSeconds);
		canvas.display();
	}

	@Override
	public void actionPerformed(ActionEvent e) {
		if(e.getActionCommand().equals(ACTION_OPEN_FILE)) {
			try {
				int dialogResponse = openLevelFileChooser.showSaveDialog(null);
				if(dialogResponse != JFileChooser.APPROVE_OPTION) {
					return;
				}
				editor.open(openLevelFileChooser.getSelectedFile());
			}
			catch(LoadingException ex) {
				logger.error("Open level failed", ex);
				JOptionPane.showMessageDialog(this,
				                              ex.getMessage(),
				                              "Open level failed",
				                              JOptionPane.ERROR_MESSAGE);
			}
		}
		else if(e.getActionCommand().equals(ACTION_NEW_FILE)) {

		}
		else if(e.getActionCommand().equals(ACTION_MISC)) {
			canvas.setRenderMisc(misc.isSelected());
		}
		else if(e.getActionCommand().equals(ACTION_FILL)) {
			canvas.setRenderFill(fill.isSelected());
		}
		else if(e.getActionCommand().equals(ACTION_PUSH)) {
			canvas.setRenderPush(push.isSelected());
		}
		else if(e.getActionCommand().equals(ACTION_SPRITE_MAP)) {
			try {
				openSpriteMap();
			}
			catch(LoadingException ex) {
				logger.error("Open sprite map failed", ex);
				JOptionPane.showMessageDialog(this,
				                              ex.getMessage(),
				                              "Open sprite map failed",
				                              JOptionPane.ERROR_MESSAGE);
			}
		}
		else if(e.getActionCommand().equals(ACTION_SAVE_FILE)) {
			try {
				int dialogResponse = saveSaveFileChooser.showSaveDialog(null);
				if(dialogResponse != JFileChooser.APPROVE_OPTION) {
					return;
				}
				editor.save(saveSaveFileChooser.getSelectedFile());
			}
			catch(LoadingException ex) {
				logger.error("Save level failed", ex);
				JOptionPane.showMessageDialog(this,
				                              ex.getMessage(),
				                              "Save level failed",
				                              JOptionPane.ERROR_MESSAGE);
			}
		}
	}

	protected DefaultDockable getPhysicalObjectsSpriteMapPanelDockable() throws LoadingException {
		File spriteMap = new File(Config.getResourcePath() + PHYSICAL_OBJECTS_SPRITE_MAP_PARENT + PHYSICAL_OBJECTS_SPRITE_MAP);
		spriteMap = new File(spriteMap.getAbsolutePath());
		if(!spriteMap.exists()) {
			throw new LoadingException("Physical objects sprite map not found at " + spriteMap.getAbsolutePath());
		}
		SpriteMapPanel spriteMapPanel = new SpriteMapPanel(spriteMap, Config.getResourcePath() + PHYSICAL_OBJECTS_THEME_PATH, PHYSICAL_OBJECTS_THEME, this, new PhysicalSpritePanelFactory());
		return new DefaultDockable(
				spriteMapPanel.getName(),
				spriteMapPanel.getScrollPane());
	}

	protected void openSpriteMap() throws LoadingException {
		File spriteMap = getSpriteMap();
		if(spriteMap == null) {
			return;
		}
		String selectedTheme = getTheme(spriteMap);
		if(selectedTheme == null) {
			return;
		}
		SpriteMapPanel spriteMapPanel = new SpriteMapPanel(spriteMap, Config.getResourcePath() + DEFAULT_THEME_PATH, selectedTheme, this, new SpritePanelFactory());
		DefaultDockable spriteMapPanelDockable =
				new DefaultDockable(
						spriteMapPanel.getName(),
						spriteMapPanel.getScrollPane(),
						spriteMapPanel.getName());
		spriteMapPanels.put(spriteMapPanelDockable.getID(), spriteMapPanelDockable);
		spriteMapsBot.addDockable(spriteMapPanelDockable, new Position(spriteMapPanels.size() - 1));
	}

	protected File getSpriteMap() {
		int openDialogResponse = spriteMapFileChooser.showOpenDialog(this);
		if(openDialogResponse != JFileChooser.APPROVE_OPTION) {
			return null;
		}
		return spriteMapFileChooser.getSelectedFile();
	}

	protected String getTheme(File spriteMap) {
		String theme = null;
		try {
			Collection<String> themes = SpriteMap.allThemes(spriteMap.getAbsolutePath());
			if(themes.size() == 1) {
				return themes.iterator().next();
			}
			theme = (String) JOptionPane.showInputDialog(
					this,
					null,
					"Select theme",
					JOptionPane.PLAIN_MESSAGE,
					new ImageIcon(),
					themes.toArray(),
					"ROFL"
			);
		}
		catch(LoadingException e) {
			JOptionPane.showMessageDialog(this,
			                              e.getMessage(),
			                              "Error loading themes",
			                              JOptionPane.ERROR_MESSAGE);

		}
		return theme;
	}

	protected JButton makeButton(String icon, String action,
	                             String toolTipText, String altText) {
		//Create and initialize the button.
		JButton button = new JButton();
		button.setActionCommand(action);
		button.setToolTipText(toolTipText);
		button.addActionListener(this);

		ImageIcon imageIcon = makeIcon(icon, altText);
		if(imageIcon != null) {                      //image found
			button.setIcon(imageIcon);
		}
		else {                                     //no image found
			button.setText(altText);
		}

		return button;
	}

	protected ImageIcon makeIcon(String icon, String descirption) {
		File iconFile = new File(Config.getResourcePath() + icon);
		if(iconFile.exists()) {                      //image found
			ImageIcon imageIcon = new ImageIcon(iconFile.getPath(), descirption);
			imageIcon.setImage(imageIcon.getImage().getScaledInstance(ICON_DIMENSION, ICON_DIMENSION, Image.SCALE_SMOOTH));
			return imageIcon;
		}
		else {                                     //no image found
			logger.error("Resource not found: " + iconFile.getAbsolutePath());
			return null;
		}
	}

	@Override
	public void spriteSelected(String spriteMap, String theme, String id, ImageIcon brushIcon) {
		canvas.setCursor(new LevelSprite(null, spriteMap, theme, id));
		canvas.setCursorDepth(Editor.GEOMETRY_DEPTH);
		setUpBrushPanel(brushIcon);
		canvas.setBrush(Brush.Geometry);
	}

	@Override
	public void bubbleSelected(Type type) {
		canvas.setBubbleBrushType(type);
		canvas.setBrush(Brush.Bubble);
		canvas.setCursorDepth(Editor.BUBBLE_DEPTH);
	}

	@Override
	public void nextSelected() {
		canvas.setBrush(Brush.Next0);
		canvas.setCursorDepth(Editor.OTHER_DEPTH);
	}

	@Override
	public void nextNextSelected() {
		canvas.setBrush(Brush.Next1);
		canvas.setCursorDepth(Editor.OTHER_DEPTH);
	}

	@Override
	public void wallVerticalSelected() {
		canvas.setBrush(Brush.VerticalWall);
	}

	@Override
	public void wallHorizontalSelected() {
		canvas.setBrush(Brush.HorizontalWall);
	}


	@Override
	public void criticalSelected() {
		canvas.setBrush(Brush.CriticalArea);
	}


	@Override
	public void killSelected() {
		canvas.setBrush(Brush.KillArea);
	}


	@Override
	public void restrictedSelected() {
		canvas.setBrush(Brush.RestrictedArea);
	}

	@Override
	public void pushSelected() {
		canvas.setCursorDepth(Editor.FRONT_DEPTH);
		canvas.setBrush(Brush.Push);
	}

	@Override
	public void fillSelected() {
		canvas.setCursorDepth(Editor.FRONT_DEPTH);
		canvas.setBrush(Brush.Fill);
	}

	@Override
	public void resultSelected() {
		canvas.setCursorDepth(Editor.FRONT_DEPTH);
		canvas.setBrush(Brush.Result);
	}

	@Override
	public void scoreSelected() {
		canvas.setCursorDepth(Editor.FRONT_DEPTH);
		canvas.setBrush(Brush.Score);
	}


	@Override
	public void nickSelected() {
		canvas.setCursorDepth(Editor.FRONT_DEPTH);
		canvas.setBrush(Brush.Nick);
	}


	@Override
	public void clockSelected() {
		canvas.setCursorDepth(Editor.FRONT_DEPTH);
		canvas.setBrush(Brush.Clock);
	}

	protected void setUpBrushPanel(ImageIcon brushIcon) {
		brushPanel.removeAll();
		brushPanel.add(new JLabel("Brush "));
		if(brushIcon == null) {
			return;
		}
		JLabel brushLabel = new JLabel(brushIcon);
		brushLabel.setBorder(BorderFactory.createEtchedBorder());
		brushPanel.add(brushLabel);
		getRootPane().updateUI();
	}
}
